package util;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.env.BasicIniEnvironment;
import org.apache.shiro.env.Environment;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;

public class SubjectAccessor {

    private SubjectAccessor() {
        // 不要生成实例
    }

    public static Subject getSubject(String classPath) {
        /*在shiro低版本中，构建安全管理器工厂IniSecurityManagerFactory并得到安全管理器实例
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:token.ini");
        SecurityManager manager = factory.getInstance();*/
        /*
         * 在shiro高版本中，以给定的ini文件的url构建基本初始化（initialized）环境
         * 封装了Shiro运作所需的所有对象的环境。所有的Shiro组件都可以从这个对象中获取
         * 环境实例通常是作为解析Shiro配置文件的结果而创建的
         * 环境实例可以存储在应用程序认为必要的任何地方，并可以从中检索在实现安全行为时可能需要的任何Shiro的组件
         */
        Environment env = new BasicIniEnvironment(classPath);
        /*
         * 获取安全管理器实例
         * SecurityManager（安全管理器）为单个应用程序中的所有对象（又称用户）执行所有安全操作
         */
        SecurityManager manager = env.getSecurityManager();
        /*
         * 设置一个虚拟机（静态）单例安全管理器，专门用于在getSubject()实现中透明使用
         * 这个方法的调用主要是为了支持框架开发。应用程序开发人员应该很少（如果有的话）需要调用这个方法
         */
        SecurityUtils.setSecurityManager(manager);
        /*
         * 获取主体
         * 主体代表单个应用程序用户的状态和安全操作
         * 这些操作包括认证（登录/注销）、授权（访问控制）以及会话访问。它是Shiro的单用户安全功能的主要机制
         * SecurityUtils.getSubject()：根据运行时环境，返回调用代码当前可用的Subject
         * 几乎所有的安全操作都应该用这个方法返回的Subject来执行
         */
        return SecurityUtils.getSubject();
    }

    public static boolean isSubjectAuthenticated(Subject subject, String username, String password) {
        /*
         * 简单的用户名/密码认证令牌，支持最广泛使用的认证机制。该类还实现了RememberMeAuthenticationToken接口，
         * 以支持跨用户会话的“记住我”服务，以及HostAuthenticationToken接口，以保留认证尝试发生的主机名或IP地址位置
         * 默认情况下，“记住我”认证是禁用的，但如果应用程序开发人员希望在登录尝试中允许它，只需调用setRememberMe(true)
         * 如果底层的SecurityManager实现也支持RememberMe服务，那么用户的身份将被跨会话记住
         * 构造一个新的用户名密码令牌，封装在认证尝试中提交的用户名和密码
         * 密码将以字符数组的形式存储于token中，通过调用getCredentials或getPassword方法获取这个数组
         */
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        try {
            /*
             * 对这个主题/用户进行登录尝试。如果不成功，就会抛出一个AuthenticationException，其子类会指明尝试失败的原因
             * 如果成功，与提交的委托人/凭证相关的账户数据将与此主题相关联，该方法将安静地返回
             * 在悄悄返回时，这个Subject实例可以被认为是经过认证的，getPrincipal()将是非空的，isAuthenticated()将为true
             */
            subject.login(token);
            /*
             * 捕捉到验证异常后打印堆栈跟踪
             * AuthenticationException：由于在认证过程中出现错误而产生的一般异常
             */
        } catch (AuthenticationException e) {
            System.out.println("由于认证过程中出现错误而产生的一般异常");
            e.printStackTrace();
            return false;
        }
        /*
         * 如果该主体/用户在当前会话中通过提供与系统已知的有效凭证相匹配的凭证来证明其身份，则返回true，否则返回false
         * 返回认证状态
         */
        return subject.isAuthenticated();
    }

    public static void checkPermissions(Subject subject, String... permissions) {
        try {
            /*
             * 确保此主体暗含着所有指定的权限字符串
             * 如果这个主体的现有关联权限没有暗示所有给定的权限，将抛出org.apache.shiro.authz.AuthorizationException
             * 这是对应类型安全的Permission变量的重载方法
             * 
             * checkPermissions方法有几个参数，就调用几次securityManager（安全管理器）的realm的doGetAuthorizationInfo方法
             * 如果不存在指定的权限，由ModularRealmAuthorizer的checkPermission方法抛出异常
             */
            subject.checkPermissions(permissions);
        } catch (AuthorizationException e) {
            System.out.println("在授权（访问控制检查）过程中出现问题");
            e.printStackTrace();
        }
    }

    public static void logout(Subject subject) {
        /*
         * 注销该主体，并使任何相关的实体无效和/或删除，例如会话和授权数据。在这个方法被调用后，主体被认为是“匿名的”，
         * 如果需要，可以继续用于另一次登录
         */
        subject.logout();
    }

}
